#include "EncryptionOperationsManager.h"
#include "ActionEncrypt.h"
#include "ActionDecrypt.h"
#include "PublicKey.h"
#include "KeyManager.h"
#include "KeyManagerException.h"
#include "ContactsManager.h"
#include "EncryptionManager.h"
#include "SelectionManager.h"
#include "Controller.h"
#include "VectorUtil.h"

#include <QFile>
#include <QTime>
#include <QDate>
#include <QDir>
#include <QDebug>

using namespace kryptos::actions;
using namespace kryptos::cryptography;
using namespace kryptos::selection;
using namespace kryptos::util;
using namespace kryptos::entities;

namespace kryptos { namespace controller {

EncryptionOperationsManager::EncryptionOperationsManager()
{
}

EncryptionOperationsManager& EncryptionOperationsManager::manager()
{
    static EncryptionOperationsManager instance;
    return instance;
}

void EncryptionOperationsManager::connectActionSignals()
{
    connect(m_currentAction.data(), SIGNAL(actionStarted())     , this, SIGNAL(encryptionOperationStarted()));
    connect(m_currentAction.data(), SIGNAL(actionProgress(int)) , this, SIGNAL(encryptionOperationProgress(int)));
    connect(m_currentAction.data(), SIGNAL(actionError(QString)), this, SIGNAL(encryptionOperationError(QString)));
}

/// Las acciones solo tienen que realizar el proceso de encriptacon o desencriptacion,
/// todo lo relacionado con chequeos y datos necesarios para realizar la accion se debe
/// realizar en el OperationsManager
/// obtener la seleccion del usuario, y en caso de ser seleccion de texto, replazarla por
/// el texto encriptado/desencriptado

//TODO: informar error de keymanager
//TODO: informar error en encriptacion
void EncryptionOperationsManager::encrypt(QMap<QString, QString> params)
{
    if (!params.isEmpty() && params.contains("recipient"))
    {
        try
        {
            SelectionManager::manager().lookForSelection();
            m_lastSelection = SelectionManager::manager().selectedData();

            if (m_lastSelection.empty())
            {
                emit usersSelectionIsEmpty();
                return;
            }

            std::vector<PublicKey>        recipientKeys;
            std::vector<SymmetricKeyInfo> symmetricKeys;

            foreach (const QString& keyid, QStringList(params.values("recipient")))
            {
                if (params.value("type") == "group")
                {
                    Group group = ContactsManager::manager().groupByName(params.value("recipient").toStdString());
                    foreach (const Contact& member, group.members())
                    {
                        recipientKeys.push_back(KeyManager::instance().findPublicKey(member.keyInfo().keyid()));

                        if (member.keyInfo().isIdentity())
                            symmetricKeys = member.symmetricKeysInfo();
                    }
                }
                else
                {
                    recipientKeys.push_back(KeyManager::instance().findPublicKey(keyid.toStdString()));
                }
            }

            qDebug() << "Selection string: " << VectorUtil::toString(m_lastSelection).c_str();

            m_lastSourceType = SelectionManager::manager().selectionIsTextSelection()
                                ? (int)ActionEncrypt::SELECTION
                                : (int)ActionEncrypt::FILE;

            m_currentAction.reset(new ActionEncrypt(m_lastSelection, (ActionEncrypt::SourceType) m_lastSourceType, recipientKeys, symmetricKeys));
            connectActionSignals();

            // connect action signals to report status
            connect(m_currentAction.data(), SIGNAL(actionEnded(bool)), this, SLOT(slotEncryptFinished(bool)));

            m_currentAction->execute();
        }
        catch (KeyManagerException& ke)
        {
            //error signal
        }
        catch (...)
        {
            //error signal
        }
    }
    else
    {
        //error signal
    }
}

//TODO: informar error si el archivo no fue encriptado para alguna de las claves identidades
//      (el usuario no puede acceder al contenido)
void EncryptionOperationsManager::decrypt(QMap<QString, QString> params)
{
    try
    {
        SelectionManager::manager().lookForSelection();
        m_lastSelection = SelectionManager::manager().selectedData();

        if (m_lastSelection.empty())
        {
            emit usersSelectionIsEmpty();
            return;
        }

        KeyInfo currentIdentityInfo;

        if (SelectionManager::manager().selectionIsFileSelection())
        {
            m_lastSourceType = (int)ActionDecrypt::FILE;
            std::string encryptedFile = VectorUtil::toStringList(m_lastSelection).front();

            currentIdentityInfo = lookForIdentityInRecipients(encryptedFile);
        }
        else
        {
            QString encryptedFileTmp = QDir::current().canonicalPath() + "//" + QDate::currentDate().toString("yy-MM-dd") + QTime::currentTime().toString("hh-mm-ss") + ".k";

            //TODO: validar seleccion, version, header, labels (formato base64)
            std::string base64encodedText   = VectorUtil::toString(m_lastSelection);
            QByteArray decodedEncryptedData = QByteArray::fromBase64(base64encodedText.c_str());

            QFile tempFile(encryptedFileTmp);
            tempFile.open(QIODevice::ReadWrite);
            tempFile.write(decodedEncryptedData);
            tempFile.flush();
            tempFile.close();

            m_lastSelection.clear();
            m_lastSelection.assign(decodedEncryptedData.data(), decodedEncryptedData.data()+decodedEncryptedData.size());
            m_lastSourceType = (int)ActionDecrypt::SELECTION;

            currentIdentityInfo = lookForIdentityInRecipients(encryptedFileTmp.toStdString());

            QFile::remove(encryptedFileTmp);
        }

        qDebug() << "Selection string: " << VectorUtil::toString(m_lastSelection).c_str();
        qDebug() << "Needs passphrase for key: " << currentIdentityInfo.name().c_str() << " (" << currentIdentityInfo.email().c_str() << ")";

        m_lastIdentityInfo = currentIdentityInfo;

        disconnect(&Controller::instance(), SIGNAL(passphraseReady(const std::string&)), this, SLOT(slotResumeDecryption(const std::string&)));
        connect(&Controller::instance(), SIGNAL(passphraseReady(const std::string&)), this, SLOT(slotResumeDecryption(const std::string&)));

        emit passphraseNeeded(m_lastIdentityInfo);
    }
    catch (KeyManagerException& ke)
    {
        //error signal
    }
}

KeyInfo EncryptionOperationsManager::lookForIdentityInRecipients(std::string encryptedFile)
{
    KeyInfo identityInfo;
    EncryptionManager manager;

    foreach(const CryptoRecipient& recipient, manager.cryptoRecipients(encryptedFile))
    {
        qDebug() << "recipient: " << recipient.id().c_str();

        if (KeyManager::instance().hasKeyWithId(recipient.id()))
        {
            identityInfo = KeyManager::instance().keyInfo(recipient.id());
        }
    }

    return identityInfo;
}

//TODO: informar error al no poseer la clave simetrica
//TODO: informar error de passphrase
//TODO: informar error de desencriptacion
void EncryptionOperationsManager::slotResumeDecryption(const std::string& passphrase)
{
    try
    {
        m_lastPassphrase = passphrase;

        PrivateKey privateKey = KeyManager::instance().findKey(m_lastIdentityInfo.keyid(), m_lastPassphrase);
        privateKey.setPassphrase(m_lastPassphrase);

        m_currentAction.reset(new ActionDecrypt(m_lastSelection, (ActionDecrypt::SourceType)m_lastSourceType, privateKey, ContactsManager::manager().allSymmetricKeys()));

        connectActionSignals();

        // connect action signals to report status
        connect(m_currentAction.data(), SIGNAL(actionEnded(bool)), this, SLOT(slotDecryptFinished(bool)));

        m_currentAction->execute();
    }
    catch (KeyManagerException& ke)
    {
        if (ke.getErrorCode() == KeyManagerException::INVALID_PASSPHRASE)
        {
            emit wrongPassphrase(m_lastIdentityInfo);
        }
        else
        {
            emit encryptionOperationError("Error decrypting file");
        }
    }
}

void EncryptionOperationsManager::slotEncryptFinished(bool success)
{
    if (success)
    {
        ActionEncrypt* encryptAction = qobject_cast<ActionEncrypt*>(m_currentAction.data());
        if (encryptAction && SelectionManager::manager().selectionIsTextSelection())
        {
            std::vector<unsigned char> encryptedSelection = encryptAction->encryptedSelection();
            std::string encodedEncryptedSelection = QString(QByteArray((const char*)&encryptedSelection[0], encryptedSelection.size()).toBase64()).toStdString();

            SelectionManager::manager().replaceSelection(encodedEncryptedSelection.c_str(), 1);
        }
    }

    emit encryptionOperationEnded();

    m_lastSourceType   = -1;
    m_lastSelection.clear();
}

void EncryptionOperationsManager::slotDecryptFinished(bool success)
{
    if (success)
    {
        ActionDecrypt* decryptAction = qobject_cast<ActionDecrypt*>(m_currentAction.data());
        if (decryptAction && SelectionManager::manager().selectionIsTextSelection())
        {
            SelectionManager::manager().replaceSelection(decryptAction->decryptedSelection().c_str(), 1);
        }
    }

    emit encryptionOperationEnded();

    // clear data user for decryption
    m_lastIdentityInfo = KeyInfo();
    m_lastSourceType   = -1;
    m_lastSelection.clear();
}



}}
